﻿using Microsoft.ExtendedReflection.Collections;
using Microsoft.ExtendedReflection.Monitoring;
using Microsoft.Moles.Framework;
using Microsoft.Moles.Framework.Engine;
using MolesVS2012.Host;
using Microsoft.VisualStudio.TestTools.Common;
using Microsoft.VisualStudio.TestTools.Execution;
using Microsoft.VisualStudio.TestTools.TestAdapter;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;

namespace MolesVS2012.Host.Host
{
    [Serializable]
    internal sealed class MolesHostAdapter : MolesAdapterBase
    {
        private readonly int timeOut = MolesVsHostEnvironmentSettings.ConnectionTimeOut.Value * 1000;
        private readonly ManualResetEvent runContextReady = new ManualResetEvent(false);
        private readonly object runnerLock = new object();
        private readonly SafeDictionary<string, ITestAdapter> adapters = new SafeDictionary<string, ITestAdapter>((IEqualityComparer<string>)StringComparer.InvariantCultureIgnoreCase);
        private AutoResetEvent workReady;
        private AutoResetEvent workDone;
        private Thread worker;
        private volatile ITestElement testElement;
        private volatile ITestContext testContext;

        private ITestAdapter[] GetAdapters()
        {
            lock (this.adapters)
                return Enumerable.ToArray<ITestAdapter>((IEnumerable<ITestAdapter>)this.adapters.Values);
        }

        private bool TryCreateAdapter(string adapterName, out ITestAdapter adapter)
        {
            lock (this.adapters)
            {
                if (!this.adapters.TryGetValue(adapterName, out adapter))
                {
                    try
                    {
                        object local_1 = Activator.CreateInstance(Type.GetType(adapterName, true));
                        adapter = (ITestAdapter)local_1;
                        this.adapters[adapterName] = adapter;
                        adapter.Initialize(this.RunContext);
                    }
                    catch (Exception exception_0)
                    {
                        this.Log.LogErrorFromException(exception_0, "Missing Wiki Topic", "Host", "Failed to initialize test adapter '{0}'", new object[1]
            {
              (object) adapterName
            });
                        this.adapters[adapterName] = adapter = (ITestAdapter)null;
                    }
                }
            }
            return adapter != null;
        }

        public override void Initialize(IRunContext runContext)
        {
            using (_ProtectingThreadContext.Acquire())
            {
                base.Initialize(SafeRunContext.CreateSafeRunContext(runContext));
                this.CheckAttachDebugger();
                this.runContextReady.Set();
                this.workReady = new AutoResetEvent(false);
                this.workDone = new AutoResetEvent(true);
                this.worker = (Thread)null;
            }
        }

        private void CheckAttachDebugger()
        {
            if (!this.TestRunConfiguration.IsExecutedUnderDebugger)
                return;
            this.Log.LogVerbose("Host", "attaching debugger");
            ExecutionUtilities.DebugTarget(this.ResultSink, this.RunGuid, new DebugTargetInfo()
            {
                ProcessId = Process.GetCurrentProcess().Id
            }, TimeSpan.FromSeconds((double)MolesVsHostEnvironmentSettings.DebuggerTimeOut.Value));
        }

        public bool WaitTillRunContextReady()
        {
            using (_ProtectingThreadContext.Acquire())
                return this.runContextReady.WaitOne(this.timeOut, false);
        }

        public override void PreTestRunFinished(IRunContext runContext)
        {
            using (_ProtectingThreadContext.Acquire())
            {
                this.Log.LogVerbose("Host", "pre test run finished");
                this.SignalWorkerFinished();
                foreach (ITestAdapter testAdapter in this.GetAdapters())
                    testAdapter.PreTestRunFinished(runContext);
            }
        }

        private bool WaitWorkerReady()
        {
            if (this.worker == null || !this.worker.IsAlive)
            {
                this.workReady.Reset();
                this.workDone.Reset();
                this.worker = new Thread(new ThreadStart(this.AsyncRunTests), MolesVsHostEnvironmentSettings.StackSize.Value * 1048576);
                this.worker.SetApartmentState(this.ApartmentState);
                this.worker.Start();
            }
            if (this.worker != null)
                return this.worker.IsAlive;
            else
                return false;
        }

        private void SignalWorkerFinished()
        {
            if (this.worker == null || !this.worker.IsAlive)
                return;
            this.Log.LogVerbose("Host", "stopping worker thread");
            this.testElement = (ITestElement)null;
            this.testContext = (ITestContext)null;
            this.workReady.Set();
            this.workDone.WaitOne(5000);
        }

        private void AsyncRunTests()
        {
            this.Log.LogVerbose("Host", "worker thread started");
            while (this.workReady.WaitOne())
            {
                try
                {
                    ITestElement testElement = this.testElement;
                    if (testElement == null)
                    {
                        this.Log.LogVerbose("Host", "worker thread finishing");
                        break;
                    }
                    else
                    {
                        ITestContext testContext = this.testContext;
                        this.RunTestAdapter(testElement, testContext);
                    }
                }
                catch (Exception ex)
                {
                    this.Log.LogErrorFromException(ex, "Missing Wiki Topic", "Host", "unhandled exception while executing test");
                }
                finally
                {
                    this.testContext = (ITestContext)null;
                    this.testElement = (ITestElement)null;
                    this.workDone.Set();
                }
            }
        }

        public override void Run(ITestElement testElement, ITestContext testContext)
        {
            lock (this.runnerLock)
            {
                if (!this.WaitWorkerReady())
                {
                    this.Log.LogError("Missing Wiki Topic", "Host", "failed to start worker thread");
                }
                else
                {
                    try
                    {
                        this.testElement = testElement;
                        this.testContext = testContext;
                        this.workReady.Set();
                    }
                    finally
                    {
                        this.workDone.WaitOne();
                    }
                }
            }
        }

        private void RunTestAdapter(ITestElement testElement, ITestContext testContext)
        {
            ITestAdapter adapter;
            IExecutionMonitor executionMonitor;
            int thread;
            using (_ProtectingThreadContext.Acquire())
            {
                this.Log.LogVerbose("Host", "running {0}", new object[1]
        {
          (object) testElement.Name
        });
                if (!this.TryCreateAdapter(testElement.Adapter, out adapter))
                {
                    this.Log.LogMessage("Host", "failed to create adapter for {0}", new object[1]
          {
            (object) testElement.Name
          });
                    return;
                }
                else
                {
                    executionMonitor = (IExecutionMonitor)new MolesExecutionMonitor();
                    thread = executionMonitor.CreateThread();
                }
            }
            try
            {
                IDisposable disposable = (IDisposable)null;
                using (_ProtectingThreadContext.Acquire())
                {
                    disposable = MolesContext.Create();
                    _ThreadContext.Start(executionMonitor, thread);
                }
                try
                {
                    adapter.Run(testElement, testContext);
                }
                finally
                {
                    using (_ProtectingThreadContext.Acquire())
                    {
                        _ThreadContext.Stop(true);
                        if (disposable != null)
                            disposable.Dispose();
                    }
                }
            }
            catch (Exception ex)
            {
                using (_ProtectingThreadContext.Acquire())
                    this.Log.LogErrorFromException(ex, "Missing Wiki Topic", "Host", "unexpected adapter exception");
            }
        }

        public override void AbortTestRun()
        {
            using (_ProtectingThreadContext.Acquire())
            {
                this.Log.LogVerbose("Host", "abort test run");
                this.SignalWorkerFinished();
                foreach (ITestAdapter testAdapter in this.GetAdapters())
                {
                    try
                    {
                        testAdapter.AbortTestRun();
                    }
                    catch (Exception ex)
                    {
                        this.Log.LogWarningFromException(ex, "Missing Wiki Topic", "Host", "adapter {0} failed AbortTestRun", new object[1]
            {
              (object) testAdapter.GetType()
            });
                    }
                }
            }
        }

        public override void Cleanup()
        {
            using (_ProtectingThreadContext.Acquire())
            {
                this.Log.LogVerbose("Host", "cleanup");
                this.SignalWorkerFinished();
                foreach (ITestAdapter testAdapter in this.GetAdapters())
                {
                    try
                    {
                        testAdapter.Cleanup();
                    }
                    catch (Exception ex)
                    {
                        this.Log.LogWarningFromException(ex, "Missing Wiki Topic", "Host", "adapter {0} failed Cleanup", new object[1]
            {
              (object) testAdapter.GetType()
            });
                    }
                }
            }
        }

        public override void PauseTestRun()
        {
            using (_ProtectingThreadContext.Acquire())
            {
                this.Log.LogVerbose("Host", "pause test run");
                foreach (IBaseAdapter baseAdapter in this.GetAdapters())
                    baseAdapter.PauseTestRun();
            }
        }

        public override void ResumeTestRun()
        {
            using (_ProtectingThreadContext.Acquire())
            {
                this.Log.LogVerbose("Host", "resume test run");
                foreach (IBaseAdapter baseAdapter in this.GetAdapters())
                    baseAdapter.ResumeTestRun();
            }
        }

        public override void ReceiveMessage(object obj)
        {
            using (_ProtectingThreadContext.Acquire())
            {
                this.Log.LogVerbose("Host", "ReceiveMessage");
                foreach (ITestAdapter testAdapter in this.GetAdapters())
                    testAdapter.ReceiveMessage(obj);
            }
        }

        public override void StopTestRun()
        {
            using (_ProtectingThreadContext.Acquire())
            {
                this.Log.LogVerbose("Host", "stop test run");
                this.SignalWorkerFinished();
                foreach (ITestAdapter testAdapter in this.GetAdapters())
                {
                    try
                    {
                        testAdapter.StopTestRun();
                    }
                    catch (Exception ex)
                    {
                        this.Log.LogWarningFromException(ex, "Missing Wiki Topic", "Host", "adapter {0} failed StopTestRun", new object[1]
            {
              (object) testAdapter.GetType()
            });
                    }
                }
            }
        }

        public bool Ping()
        {
            using (_ProtectingThreadContext.Acquire())
                return true;
        }
    }
}
